/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.peterkuterna.android.apps.devoxxfrsched.util;

import android.graphics.Rect;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;

/**
 * {@link TouchDelegate} that gates {@link MotionEvent} instances by comparing
 * then against fractional dimensions of the source view.
 * <p>
 * This is particularly useful when you want to define a rectangle in terms of
 * the source dimensions, but when those dimensions might change due to pending
 * or future layout passes.
 * <p>
 * One example is catching touches that occur in the top-right quadrant of
 * {@code sourceParent}, and relaying them to {@code targetChild}. This could be
 * done with: <code>
 * FractionalTouchDelegate.setupDelegate(sourceParent, targetChild, new RectF(0.5f, 0f, 1f, 0.5f));
 * </code>
 */
public class FractionalTouchDelegate extends TouchDelegate {

	private View mSource;
	private View mTarget;

	private RectF mSourceFraction;

	private Rect mScrap = new Rect();

	/** Cached full dimensions of {@link #mSource}. */
	private Rect mSourceFull = new Rect();
	/** Cached projection of {@link #mSourceFraction} onto {@link #mSource}. */
	private Rect mSourcePartial = new Rect();

	private boolean mDelegateTargeted;

	public FractionalTouchDelegate(View source, View target,
			RectF sourceFraction) {
		super(new Rect(0, 0, 0, 0), target);
		mSource = source;
		mTarget = target;
		mSourceFraction = sourceFraction;
	}

	/**
	 * Helper to create and setup a {@link FractionalTouchDelegate} between the
	 * given {@link View}.
	 * 
	 * @param source
	 *            Larger source {@link View}, usually a parent, that will be
	 *            assigned {@link View#setTouchDelegate(TouchDelegate)}.
	 * @param target
	 *            Smaller target {@link View} which will receive
	 *            {@link MotionEvent} that land in requested fractional area.
	 * @param sourceFraction
	 *            Fractional area projected onto source {@link View} which
	 *            determines when {@link MotionEvent} will be passed to target
	 *            {@link View}.
	 */
	public static void setupDelegate(View source, View target,
			RectF sourceFraction) {
		source.setTouchDelegate(new FractionalTouchDelegate(source, target,
				sourceFraction));
	}

	/**
	 * Consider updating {@link #mSourcePartial} when {@link #mSource}
	 * dimensions have changed.
	 */
	private void updateSourcePartial() {
		mSource.getHitRect(mScrap);
		if (!mScrap.equals(mSourceFull)) {
			// Copy over and calculate fractional rectangle
			mSourceFull.set(mScrap);

			final int width = mSourceFull.width();
			final int height = mSourceFull.height();

			mSourcePartial.left = (int) (mSourceFraction.left * width);
			mSourcePartial.top = (int) (mSourceFraction.top * height);
			mSourcePartial.right = (int) (mSourceFraction.right * width);
			mSourcePartial.bottom = (int) (mSourceFraction.bottom * height);
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		updateSourcePartial();

		// The logic below is mostly copied from the parent class, since we
		// can't update private mBounds variable.

		// http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;
		// f=core/java/android/view/TouchDelegate.java;hb=eclair#l98

		final Rect sourcePartial = mSourcePartial;
		final View target = mTarget;

		int x = (int) event.getX();
		int y = (int) event.getY();

		boolean sendToDelegate = false;
		boolean hit = true;
		boolean handled = false;

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			if (sourcePartial.contains(x, y)) {
				mDelegateTargeted = true;
				sendToDelegate = true;
			}
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_MOVE:
			sendToDelegate = mDelegateTargeted;
			if (sendToDelegate) {
				if (!sourcePartial.contains(x, y)) {
					hit = false;
				}
			}
			break;
		case MotionEvent.ACTION_CANCEL:
			sendToDelegate = mDelegateTargeted;
			mDelegateTargeted = false;
			break;
		}

		if (sendToDelegate) {
			if (hit) {
				event.setLocation(target.getWidth() / 2, target.getHeight() / 2);
			} else {
				event.setLocation(-1, -1);
			}
			handled = target.dispatchTouchEvent(event);
		}
		return handled;
	}
}
